﻿using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Linq;
using System.Web.Security;
using System.Web.UI.HtmlControls;
using System.Collections.Generic;
using TA.JSON;
using System.IO;
using System.Web.UI;
using System.ComponentModel;
using System.Text;
using System.Text.RegularExpressions;
using System.Diagnostics;
using System.Xml;
using System.Collections.Specialized;
using System.Reflection;
using System.Threading;
using System.Collections;
using TA.Turbo.Controls;
using TA.DotNetUtils;

namespace TA.Turbo
{
	public partial class TPage
	{
		#region VARS
		#region CONTAINERS
		Control _specialContainer = new Control();
		LiteralControl _stylesControl = new LiteralControl(),
			_scriptIncludesControl = new LiteralControl(),
			_scriptStartupControl = new LiteralControl();
		#endregion
		#region LISTS
		Dictionary<string, BaseInclude> _allIncludes = new Dictionary<string, BaseInclude>();
		List<Manager> _managers = new List<Manager>();
		List<TurboProgress> _progressControls = new List<TurboProgress>();
		ScenarioCollection _scenarios;
		IncludeCollection _includes = new IncludeCollection();
		ControlCollection<GlobalControlReference> _globalControls;
		ControlCollection<ControlReferenceGroup> _controlReferenceGroups;
		List<BaseValidator> _validators = new List<BaseValidator>();
		List<IHashStringPartProvider> _hashStringProviders = new List<IHashStringPartProvider>();
		internal List<string> _uniqueCookies = new List<string>();
		Dictionary<string, TControl> _controls = new Dictionary<string, TControl>();
		List<PostScenario> _allScenarios = new List<PostScenario>();
		List<PageEvent> _allPageEventsList = new List<PageEvent>();
		Dictionary<string, PageEvent> _allPageEvents = null;//new Dictionary<string, PageEvent>();
		List<IClientId> _allClientIds = new List<IClientId>();
		List<IHasControlState> _controlsRequiringControlState = new List<IHasControlState>();
		//List<Script> _optionalScripts = new List<Script>();
		#endregion
		#region CONTROLS
		Dictionary<char, int> _uniqueIdIndexes = new Dictionary<char, int>();
		Dictionary<string, string> _uniqueIds = new Dictionary<string, string>();
		#endregion
		#region SCRIPTS
		JSObfuscator _obfuscator = new JSObfuscator();
		//LiteralControl _litLinksInit;
		#endregion
		Dictionary<string, object> _pageItems = new Dictionary<string, object>();
		internal bool _initDone = false;
		BrowserType _browserType;
		#endregion
		#region PROPS
		internal List<PageEvent> AllPageEventsList
		{
			get
			{
				return this._allPageEventsList;
			}
		}
		internal Dictionary<string, PageEvent> AllPageEvents
		{
			get
			{
				if (this._allPageEvents == null)
				{
					this._allPageEvents = new Dictionary<string, PageEvent>();
					foreach (PageEvent pe in this.AllPageEventsList)
					{
						this._allPageEvents[pe.UniqueID] = pe;
					}
				}
				return this._allPageEvents;
			}
		}
		internal List<IClientId> AllClientIds
		{
			get
			{
				return this._allClientIds;
			}
		}
		#endregion
		#region METHODS
		internal static void AddControl(TControl baseControl)
		{
			if (TPage.Current == null) return;
			TPage.Current._controls[baseControl.UniqueID] = baseControl;
		}
		string GetNextUniqueId(char c)
		{
			int n;
			if (!this._uniqueIdIndexes.TryGetValue(c, out n))
			{
				n = 0;
			}
			string res = c.ToString() + (n++).ToString();
			this._uniqueIdIndexes[c] = n;
			return res;
		}
		internal static string GetOptimizedUniqueId(ref string uniqueId, string defaultUniqueId, bool optimize, char c)
		{
			TPage t = TPage.Current;
			if (t != null && t.OptimizeIDs && optimize)
			{
				if (uniqueId == null)
				{
					if (!t._uniqueIds.TryGetValue(defaultUniqueId, out uniqueId))
					{
						t._uniqueIds[defaultUniqueId] = uniqueId = t.GetNextUniqueId(c);
					}
				}
				return uniqueId;
			}
			else
				return defaultUniqueId;
		}
		internal void AddIncludes(IncludeCollection includes)
		{
			this.AddIncludes(includes.Values);
		}
		internal void AddIncludes(IEnumerable<BaseInclude> includes)
		{
			if (!this._initDone)
			{
				foreach (BaseInclude one in includes)
				{
					//if (this.AllIncludes.ContainsKey(one.FileName))
					//{
					//  this.AllIncludes.Remove(one.FileName);
					//}
					//this.AllIncludes[one.FileName] = one;

					if (!this.AllIncludes.ContainsKey(one.FileName))
						this.AllIncludes[one.FileName] = one;
				}
			}
		}
		private void InitPage()
		{
			this.Init_BrowserType();

			this.FrameworkInitialize();
#if DEBUG
			Log("TPage._Init FrameworkInitialize DONE");
#endif

			mPerformPreInit.Invoke(this, new object[] { });
#if DEBUG
			Log("TPage._Init PerformInit DONE");
#endif

			this.Controls.Add(this._specialContainer);

			//if (this.Context.IsDebuggingEnabled)
			//  Init_Recompile();

			this.Scenarios.Add(new PostScenario
			{
				ID = EmptyPostScenarioId,
				CausesValidation = false,
			});

			mInitRecursive.Invoke(this, new object[] { null });
#if DEBUG
			Log("TPage._Init InitRecursive DONE");
#endif

			// now add includes from TPage
			this.AddIncludes(this.Includes);

			// now add includes from Managers
			foreach (Manager mgr in this.Managers)
			{
				this.AddIncludes(mgr.Includes);
			}

			this.SaveForReset();
			foreach (TControl ctl in this._controls.Values)
			{
				ctl.SaveForReset();
			}

#if DEBUG
			Log("TPage._Init SaveForReset DONE");
#endif

			Init_Scripts();
#if DEBUG
			Log("TPage._Init Includes DONE");
#endif

			Init_Styles();
#if DEBUG
			Log("TPage._Init Styles DONE");
#endif

			this._initDone = true;
		}

		private void Init_BrowserType()
		{
			this._browserType = this.Request.Browser.GetBrowserType();
		}
		private void SaveForReset()
		{
			foreach (string propName in this._controlState.Keys)
			{
				this._controlStateForReset[propName] = this._controlState[propName];
			}
		}
		private void Init_Styles()
		{
			string stylesCacheKey = "Styles" + this.Request.Path + this.IncludesHashCode;
			if (this.Cache[stylesCacheKey] == null)
			{
				StringBuilder sbStyles = new StringBuilder();

				sbStyles.Append(this.Init_Styles_Includes());

				this.Cache[stylesCacheKey] = sbStyles.ToString();
			}

			this._stylesControl.Text = (string)this.Cache[stylesCacheKey];
			Page.Header.Controls.AddAt(0, this._stylesControl);
		}
		private string Init_Styles_Includes()
		{
			if (this.CombineStyles)
			{
				return Init_Styles_Includes_Combined();
			}
			else
			{
				return Init_Styles_Includes_Separate();
			}
		}
		private string Init_Styles_Includes_Separate()
		{
#if DEBUG
			Log("TurboPage.Init_Styles_Includes_Separate BEGIN");
#endif

			StringBuilder sb = new StringBuilder();
			foreach (BaseInclude si in AllIncludes.Values)
			{
				if (si is IStyleInclude)
				{
					sb.AppendStyle(si.GetClientUrl());
				}
			}

#if DEBUG
			Log("TurboPage.Init_Styles_Includes_Separate END");
#endif

			return sb.ToString();
		}
		private string Init_Styles_Includes_Combined()
		{
#if DEBUG
			Log("TurboPage.Init_Styles_Includes_Combined BEGIN");
#endif

			StringBuilder sbScripts = new StringBuilder();

			Dictionary<string, StringBuilder> styleTextsByPaths = new Dictionary<string, StringBuilder>();

			// for all style includes
			foreach (BaseInclude si in AllIncludes.Values)
			{
				if (si is IStyleInclude)
				{
					// determine path (for relative path purposes)

					// by default, set to ""
					string path = "";

					if (si is BaseResourceInclude)
					{ // do nothing, leave it at ""
					}
					else if (si is BaseFileInclude)
					{
						// here we set it from the include path
						path = rgxGetPath.Match(((BaseFileInclude)si).FileName).Value;
					}

					// with path, we ensure that we have a StringBuilder of all styles on that path
					if (!styleTextsByPaths.ContainsKey(path))
						styleTextsByPaths[path] = new StringBuilder();

					styleTextsByPaths[path].Append(si.Content);
					styleTextsByPaths[path].Append("\r\n");
				}
			}

			// the resource includes are path-agnostic; we can add them to one of the existing pathes
			StringBuilder sbResourceIncludes;
			if (styleTextsByPaths.TryGetValue("", out sbResourceIncludes))
			{
				// the "" path can't be used of course
				styleTextsByPaths.Remove("");

				// let's find where to move them
				string destPath;
				if (styleTextsByPaths.Count == 0)
				{
					// if nothing exists, create empty at root
					destPath = "~/";
					styleTextsByPaths[destPath] = new StringBuilder();
				}
				else
				{
					// if anything, then just use first one
					destPath = styleTextsByPaths.Keys.First();
				}

				StringBuilder sbDest = styleTextsByPaths[destPath];
				sbDest.Append(sbResourceIncludes.ToString());
			}

			CombinedContent contStyles;

			foreach (string path in styleTextsByPaths.Keys)
			{
				contStyles = CombinedContent.Get(styleTextsByPaths[path].ToString(), "text/css");

				sbScripts.AppendStyle(DoResolveUrl(string.Format("{1}TurboResource.axd?k={0}", contStyles.Key, path)));
			}

#if DEBUG
			Log("TurboPage.Init_Styles_Includes_Combined END");
#endif

			return sbScripts.ToString();
		}
		private void Init_Scripts()
		{
			this.Init_Scripts_Includes();

			this.ClientScript.RegisterClientScriptInclude(
				"TA.Turbo.TPage.Init",
				Init_Scripts_MakeCCUrl(CombinedContent.Get(this.Init_Scripts_Initialization_Get(), "text/javascript"))
				);

			this.Init_Scripts_Post();
		}
		void Init_Scripts_Includes()
		{
			string scriptIncludesCacheKey = "Scripts" + this.Request.Path + this.IncludesHashCode;
#if DEBUG
#else
			if (this.Cache[scriptIncludesCacheKey] == null)
#endif
			{
				this.Cache[scriptIncludesCacheKey] = this.Init_Scripts_Includes_Get();
			}

			this._scriptIncludesControl.Text = (string)this.Cache[scriptIncludesCacheKey];
			Page.Header.Controls.AddAt(0, this._scriptIncludesControl);
		}

		private void Init_Scripts_Append(StringBuilder sbScriptContents, StringBuilder sbScripts, bool combine, bool optimize, Func<string> getContents, Func<string> getUrl)
		{
			if (combine && this.CombineScripts)
			{
				string contents = getContents();
				if (optimize && this.OptimizeScripts)
				{
					contents = this.Obfuscator.Obfuscate(contents, this.OptimizeScripts);
				}
				sbScriptContents.Append(contents);
				sbScriptContents.Append("\r\n");
			}
			else
			{
				if (sbScriptContents.Length > 0)
				{
					TPage.Init_Scripts_MakeCC(sbScriptContents, sbScripts);

					sbScriptContents.Remove(0, sbScriptContents.Length);
				}

				sbScripts.AppendScript(getUrl());
			}
		}
		private static void Init_Scripts_MakeCC(StringBuilder sbScriptContents, StringBuilder sbScripts)
		{
			CombinedContent contScripts = CombinedContent.Get(sbScriptContents.ToString(), "text/javascript");
			sbScripts.AppendScript(TPage.Init_Scripts_MakeCCUrl(contScripts));
		}
		private static string Init_Scripts_MakeCCUrl(string script)
		{
			return Init_Scripts_MakeCCUrl(CombinedContent.Get(script, "text/javascript"));
		}
		private static string Init_Scripts_MakeCCUrl(CombinedContent contScripts)
		{
			return TPage.DoResolveUrl(string.Format("~/TurboResource.axd?k={0}", contScripts.Key));
		}
		private static void Init_Scripts_Finalize(StringBuilder sbScriptContents, StringBuilder sbScripts)
		{
			if (sbScriptContents.Length > 0)
			{
				TPage.Init_Scripts_MakeCC(sbScriptContents, sbScripts);
			}
		}

		private string Init_Scripts_Includes_Get()
		{
			StringBuilder sbScripts = new StringBuilder(); // this holds the <script> tags
			StringBuilder sbScriptContents = new StringBuilder(); // this holds script itself

			foreach (BaseInclude si in AllIncludes.Values)
			{
				if (si is IScriptInclude)
				{
					this.Init_Scripts_Append(
						sbScriptContents, sbScripts,
						si.Combine, si.Optimize,
						() => si.Content,
						() => si.GetClientUrl());
				}
			}

			TPage.Init_Scripts_Finalize(sbScriptContents, sbScripts);

			return sbScripts.ToString();
		}

		private void Init_Scripts_Initialization(StringBuilder sbScriptContents, StringBuilder sbScripts)
		{
			this.Init_Scripts_Append(
				sbScriptContents, sbScripts,
				true, true,
				this.Init_Scripts_Initialization_Get,
				() => Init_Scripts_MakeCCUrl(this.Init_Scripts_Initialization_Get()));
		}
		private string Init_Scripts_Initialization_Get()
		{
			StringBuilder sb = new StringBuilder();

			this.Init_Scripts_Initialization_Scenarios(sb);
			this.Init_Scripts_Initialization_Validators(sb);
			this.Init_Scripts_Initialization_JSCookie(sb);
			this.Init_Scripts_Initialization_TurboInit(sb);

			return sb.ToString();
		}
		private void Init_Scripts_Initialization_Scenarios(StringBuilder sb)
		{
#if DEBUG
			Log("TurboPage.Init_Scripts_Initialization_Scenarios BEGIN");
#endif

			if (this.AllScenarios.Count > 0)
			{
				sb.Append(this.Obfuscator.Obfuscate("TA.Turbo.Common.PostScenarios = ", this.OptimizeScripts));

				JsonWriter jw = new JsonWriter(sb);

				jw.StartObject();
				foreach (PostScenario scenario in AllScenarios)
				{
					jw.PropertyName(scenario.ClientID);
					scenario.Serialize(jw);
				}
				jw.EndObject();
				sb.AppendLine(";");
			}

#if DEBUG
			Log("TurboPage.Init_Scripts_Initialization_Scenarios END");
#endif
		}
		private void Init_Scripts_Initialization_Validators(StringBuilder sb)
		{
#if DEBUG
			Log("TurboPage.Init_Scripts_Initialization_Validators BEGIN");
#endif

			if (this.Validators.Any(v => v.IsServerValidated))
			{
				sb.Append(this.Obfuscator.Obfuscate("TA.Turbo.Common.Validators = ", this.OptimizeScripts));

				JsonWriter jw = new JsonWriter(sb);
				jw.StartArray();

				foreach (BaseValidator v in Validators)
				{
					if (v.IsServerValidated)
					{
						jw.Value(v.ClientID);
					}
				}

				jw.EndArray();

				sb.Append(";");
			}

#if DEBUG
			Log("TurboPage.Init_Scripts_Initialization_Validators END");
#endif
		}
		private void Init_Scripts_Initialization_JSCookie(StringBuilder sb)
		{
			sb.Append(@"document.cookie = ""JS=1; path=/"";
document.cookie = ""NOJS=1; expires=Tue, 01 Feb 2000 05:00:00 GMT; path=/"";
");
		}
		private void Init_Scripts_Initialization_TurboInit(StringBuilder sb)
		{
			sb.Append(this.Obfuscator.Obfuscate(
					string.Format(
						"if(typeof(TA) != \"undefined\") TA.Turbo.Common.Init(\"{0}\", \"{1}\");",
						Form.UniqueID,
						ResolveUrl("~/")),
					this.OptimizeScripts));
		}
		private void Init_Scripts_Post()
		{
			StringBuilder sbScripts = new StringBuilder(); // this holds the <script> tags
			StringBuilder sbScriptContents = new StringBuilder(); // this holds script itself

			this.Init_Scripts_Post_LinksInit(sbScriptContents, sbScripts);
			this.Init_Scripts_Post_ClientIds(sbScriptContents, sbScripts);

			Init_Scripts_Finalize(sbScriptContents, sbScripts);

			//this._litLinksInit = new LiteralControl(sbScripts.ToString());
			this._scriptStartupControl.Text = sbScripts.ToString();
			this._specialContainer.Controls.Add(this._scriptStartupControl);

		}

		private void Init_Scripts_Post_LinksInit(StringBuilder sbScriptContents, StringBuilder sbScripts)
		{
			this.Init_Scripts_Append(sbScriptContents, sbScripts, true, true,
				() => JSLinksInit,
				() => Init_Scripts_MakeCCUrl(JSLinksInit));
		}
		private void Init_Scripts_Post_ClientIds(StringBuilder sbScriptContents, StringBuilder sbScripts)
		{
			//StringBuilder sb = new StringBuilder("<script type=\"text/javascript\">\r\n");
			foreach (BaseClientId c in this.AllClientIds)
			{
				string script = c.GetScript();
				if (script != null)
					sbScriptContents.AppendLine(TPage.PrepareScript(script));
			}
			foreach (PageEvent pe in this.AllPageEvents.Values)
			{
				sbScriptContents.AppendLine(TPage.PrepareScript(pe.GetScript()));
			}
			foreach (PostScenario ps in this.AllScenarios)
			{
				sbScriptContents.AppendLine(TPage.PrepareScript(ps.GetScript()));
			}
		}
		#endregion
	}
}